﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Timers;
using System.Windows.Media;

using GravityBallScreenSaver.Bodies;

namespace GravityBallScreenSaver.Gravity
{
    public class Gravity : IGravity
    {
        private float Fg;
        private int diameter;
        protected Ball[] _planets;
        protected uint _step;
        protected Timer iTimer;

        public int Width { get; set; }
        public int Height { get; set; }
 
        public Ball[] planets 
        { 
            get 
            { 
                return _planets; 
            } 
        }

        public uint step
        {
            get
            {
                return _step;
            }
            protected set
            {
                _step = value;
                if (_step >= Bodies.Ball.TAIL_LENGTH)
                    _step = 0;
                else if (_step < 0)
                    _step = Bodies.Ball.TAIL_LENGTH;
            }
        }       

        public Gravity(int width, int height)
        {
            this.Initialize(width, height);

            //Set variables
            diameter = (int)Properties.Settings.Default.PlanetDiameter;
            Fg = (float)Properties.Settings.Default.Gravity;

             // Create a timer
            double elapsedTime = 1000.0 / Properties.Settings.Default.FramesPerSecond;
            iTimer = new Timer(elapsedTime);            
            iTimer.Elapsed += OniTimerEvent;          
        }

        public void Initialize(int width, int height)
        {
            this.Width = width;
            this.Height = height;
            
            _planets = new Ball[Properties.Settings.Default.BallCount];

            for (int i = 0; i < Properties.Settings.Default.BallCount; i++)
            {
                _planets[i] = new Ball(width, height);
                _planets[i].baseColor = Color.FromArgb(255, (byte)(Math.Log10(_planets[i].mass * (float)Properties.Settings.Default.MassMax / 255f)), (byte)((_planets[i].Vx0 + (float)Properties.Settings.Default.Speed) * 128f / (float)Properties.Settings.Default.Speed), (byte)((_planets[i].Vy0 + (float)Properties.Settings.Default.Speed) * 128f / (float)Properties.Settings.Default.Speed));
            }
        }

        public void Iterate()
        {
            this.step++;

            for (int i = 0; i < _planets.Length; i++)
            {
                Ball bCurrent = _planets[i];
                bCurrent.Px[step] = bCurrent.Px0;
                bCurrent.Py[step] = bCurrent.Py0;
                //Note, this is where we would normally maintain a history of
                //velocity and accelleration
                //but for the basic version of gravity
                //we don't track it to simplify the calculations.                

                for (int j = 0; j < _planets.Length; j++)
                {
                    if (i != j)
                    {
                        Ball bCheck = _planets[j];
                        if (Math.Abs(bCurrent.Px0 - bCheck.Px0) < diameter &&
                            Math.Abs(bCurrent.Py0 - bCheck.Py0) < diameter)
                        {
                            //bounce the balls
                            //Or at the very minimum, turn off gravity at this close proximity to prevent extreme behavior
                        }
                        else
                        {
                            //Gravity the balls
                            float R = (float)Math.Sqrt(Math.Pow(bCurrent.Px0 - bCheck.Px0, 2) + Math.Pow(bCurrent.Py0 - bCheck.Py0, 2));
                            //float G = -1 * Fg * bCurrent.mass * bCheck.mass / (float)Math.Pow(R, 2);
                            float G = -1 * Fg * bCheck.mass / (float)Math.Pow(R, 2);

                            bCurrent.Vx0 += G * (bCurrent.Px0 - bCheck.Px0) / R;
                            bCurrent.Vy0 += G * (bCurrent.Py0 - bCheck.Py0) / R;
                        }
                    }
                }
                if (bCurrent.Px0 < 0 || bCurrent.Px0 + diameter > this.Width)
                {
                    //Bounce against side of screen
                    bCurrent.Vx0 *= -1F * bCurrent.elasticity;
                }

                if (bCurrent.Py0 < 0 || bCurrent.Py0 + diameter > this.Height)
                {
                    //Bounce against top/bottom of screen
                    bCurrent.Vy0 *= -1F * bCurrent.elasticity;
                }

                if (bCurrent.Px0 < 0)
                    bCurrent.Px0 = 0;
                if (bCurrent.Px0 + diameter > this.Width)
                    bCurrent.Px0 = this.Width - diameter;
                if (bCurrent.Py0 < 0)
                    bCurrent.Py0 = 0;
                if (bCurrent.Py0 + diameter > this.Height)
                    bCurrent.Py0 = this.Height - diameter;

                _planets[i] = bCurrent;
            }

            for (int i = 0; i < _planets.Length; i++)
            {
                _planets[i].Px0 += _planets[i].Vx0;
                _planets[i].Py0 += _planets[i].Vy0;
            }
        }

        #region TimerController

        protected void OniTimerEvent(object source, ElapsedEventArgs e)
        {
            Iterate();

            //Only raise the event outside us if we tripped on our own timer
            //This will allow the Iterate function to be overridden, calling
            //the base function (in Gravity) to do the basic manipulation
            //and then any advanced manipulation by the Gravity inheritor
            //for things such as Accelleration, Color Shift, etc.
            GravityEventArgs ge = new GravityEventArgs(step);
            OnStep(ge);
        }

        public void Start()
        {
            iTimer.Start();
        }

        public void Stop()
        {
            iTimer.Stop();
        }

        #endregion

        #region RaiseEvent

        public event GravityEventHandler Step;

        protected virtual void OnStep(GravityEventArgs e)
        {
            GravityEventHandler handler = Step;
            if (handler != null)
            {
                // Invokes the delegates. 
                handler(this, e);
            }
        }

        #endregion
    }
}
